RMI全称是Remote Method Invocation,远程方法调用,从这个名字就可以看出,他的目标和RPC其实是类似的,是让某个Java虚拟机上的对象调⽤另⼀个Java虚拟机中对象上的⽅法,只不过RMI是Java独有的⼀种机制RMI Server:
package aaaa.rmi; import java.rmi.Naming; import java.rmi.Remote; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.server.UnicastRemoteObject; public class RMIServer { public interface IRemoteHelloWorld extends Remote { public String hello() throws RemoteException; } public class RemoteHelloWorld extends UnicastRemoteObject implements IRemoteHelloWorld { protected RemoteHelloWorld() throws RemoteException { super(); } public String hello() throws RemoteException { System.out.println("call from"); return "Hello world"; } } private void start() throws Exception { RemoteHelloWorld h = new RemoteHelloWorld(); LocateRegistry.createRegistry(1099); Naming.rebind("rmi://127.0.0.1:1099/Hello", h); } public static void main(String[] args) throws Exception { new RMIServer().start(); } }⼀个
RMI Server分为三部分:- ⼀个继承了
java.rmi.Remote的接⼝,其中定义我们要远程调⽤的函数,比如这里的hello() - ⼀个实现了此接⼝的类
- ⼀个主类,⽤来创建
Registry,并将上⾯的类实例化后绑定到⼀个地址,这就是所谓的Server了
- ⼀个继承了
RMI Client:
package aaaa.rmi; import java.rmi.Naming; import java.rmi.NotBoundException; import java.rmi.RemoteException; public class RMIClient { public static void main(String[] args) throws Exception { RMIServer.IRemoteHelloWorld hello = (RMIServer.IRemoteHelloWorld) Naming.lookup("rmi://192.168.128.178:1099/Hello"); String ret = hello.hello(); System.out.println( ret); } }使⽤
Naming.lookup在Registry中寻找到名字是Hello的对象,后面的使用就和在本地使用⼀样了一个RMI过程有以下三个参与者:
RMI Registry、RMI Server、RMI Client通常我们在新建一个
RMI Registry的时候,都会直接绑定一个对象在上面,也就是说我们示例代码中的Server其实包含了Registry和Server两部分LocateRegistry.createRegistry(1099); //创建并运行RMI Registry Naming.bind("rmi://127.0.0.1:1099/Hello", new RemoteHelloWorld()); //行将RemoteHelloWorld对象绑定到Hello这个名字上Naming.bind的第一个参数是一个URL,形如:rmi://host:port/name,其中,host和port就是RMI Registry的地址和端口,name是远程对象的名字如果
RMI Registry在本地运行,那么host和port是可以省略的,此时host默认是localhost,port默认是1099:Naming.bind("Hello", new RemoteHelloWorld());
攻击RMI Registry
Java对远程访问
RMI Registry做了限制,只有来源地址是localhost的时候,才能调用rebind、bind、unbind等方法不过
list和lookup方法可以远程调用,list方法可以列出目标上所有绑定的对象:String[] s = Naming.list("rmi://192.168.128.178:1099");lookup作用就是获得某个远程对象,那么,只要目标服务器上存在一些危险方法,通过RMI就可以对其进行调用
RMI利用codebase执行任意代码
Java Applet是可以运行在浏览器中的,在使用Applet的时候通常需要指定一个codebase属性:<applet code="HelloWorld.class" codebase="Applets" width="800" height="600"> </applet>除了
Applet,RMI中也存在远程加载的场景,也会涉及到codebase,codebase是一个地址,告诉Java虚拟机应该从哪个地方去搜索类,有点像CLASSPATH,但CLASSPATH是本地路径,而codebase通常是远程URL,比如http、ftp等如果指定
codebase=http://example.com/,然后加载org.vulhub.example.Example类,则Java虚拟机会下载这个文件http://example.com/org/vulhub/example/Example.class,并作为Example类的字节码RMI的流程中,客户端和服务端之间传递的是一些序列化后的对象,这些对象在反序列化时,就会去寻找类,如果某一端反序列化时发现一个对象,那么就会去自己的
CLASSPATH下寻找想对应的类;如果在本地没有找到这个类,就会去远程加载codebase中的类如果
codebase被控制,就可以加载恶意类了在RMI中,可以将
codebase随着序列化数据一起传输,服务器在接收到这个数据后就会去CLASSPATH和指定的codebase寻找类,由于codebase被控制导致任意命令执行漏洞漏洞条件:
- 安装并配置了
SecurityManager - Java版本低于7u21、6u45,或者设置了
java.rmi.server.useCodebaseOnly=false - 在
java.rmi.server.useCodebaseOnly配置为 true 的情况下,Java虚拟机将只信任预先配置好的codebase,不再支持从RMI请求中获取
- 安装并配置了
看着P神的文章想跟着复现一下不知道抽什么风一直报错,放弃了,我选择嗯看
研究一下他的源码,大概意思就是,先起一个
RMIServer,在编译运行的时候设置三个参数:java.rmi.server.hostname=192.168.135.142 //是服务器的IP地址,远程调用时需要根据这个值来访问RMI Server java.rmi.server.useCodebaseOnly=false //支持从RMI请求中获取Codebase java.security.policy=client.policy //授权再建立一个RMIClient.java,在另一个位置运行,此时
RMI Server在本地CLASSPATH里找不到类,会去加载Codebase中的类import java.rmi.Naming; import java.util.List; import java.util.ArrayList; import java.io.Serializable; public class RMIClient implements Serializable { public class Payload extends ArrayList<Integer> {} public void lookup() throws Exception { ICalc r = (ICalc) Naming.lookup("rmi://192.168.135.142:1099/refObj"); List<Integer> li = new Payload(); li.add(3); li.add(4); System.out.println(r.sum(li)); } public static void main(String[] args) throws Exception { new RMIClient().lookup(); } }查看
example.com的日志,慧收到了来自Java的请求/RMIClient$Payload.class,因为还没有实际放置这个类文件,所以会出现异常只需要编译一个恶意类,将其class文件放置在Web服务器的
/RMIClient$Payload.class即可